昨天把 Feed 的核心邏輯做出來了,GET /feed 已經能正確回傳 Pikachu 追蹤的人發的貼文,但那時候刻意跳過了一個問題:這支 API 到底該回傳什麼形狀的資料?
目前的做法很直覺,後端把 Post 撈出來,整包塞進 JSON 回傳:
{
"posts": [
{
"id": 101,
"author_id": 42,
"content": "Hello PokeThreads",
"created_at": "2026-01-01T00:00:00Z"
}
],
"next_cursor": "abc123"
}
這種「一個資源、一個固定形狀」的 API 設計方式,就是 REST(Representational State Transfer)。
今天想討論的是:RESTful API 會在哪裡開始卡住?
REST 的核心概念很單純,把系統拆成一個個「資源(Resource)」,每個資源有自己的網址,再搭配 HTTP 動詞表示要做什麼:
GET /posts 取得貼文列表
GET /posts/:id 取得單一貼文
POST /posts 建立貼文
GET /feed 取得 Feed
POST /follow 建立追蹤關係
這正是我們這幾天一路在用的設計方式:資源清楚、動詞固定、每支 API 的輸入輸出都是事先定義好的固定格式。對後端來說非常好懂,也很容易讓 CDN、瀏覽器對 GET 做 HTTP Cache,這點很重要,後面會再提到。
先看看 REST 在 PokeThreads 這種場景下,實際上會遇到什麼狀況。
假設手機版的 PokeThreads App,畫面上的 Feed 每則貼文只需要顯示「內容」跟「作者暱稱」兩個欄位,但 GET /feed 回傳的卻是完整的 Post 物件:
{
"id": 101,
"author_id": 42,
"content": "Hello PokeThreads",
"created_at": "2026-01-01T00:00:00Z",
"media_url": null,
"like_count": 12,
"reply_count": 3
}
前端只用得到 content,其他欄位全部白白傳輸、白白解析,這就是 Over-fetching:拿到的資料比實際需要的還多。
單一一則貼文看起來還好,但 Feed 一次回傳 20 則,乘上大量使用者、大量 Request,多出來的頻寬跟解析成本就不可忽略了。
反過來,如果畫面除了貼文內容,還要顯示作者的頭像跟粉絲數,GET /feed 只回傳 author_id,前端就得再對每個作者額外呼叫一次 GET /users/:id:
GET /feed
↓
拿到 20 篇貼文,20 個 author_id
↓
GET /users/42
GET /users/87
GET /users/103
...(最多可能到 20 次)
這就是 Under-fetching:一支 API 給的資料不夠,前端得再發好幾次 Request 才能拼出畫面要的資料,也就是常聽到的 N+1 Request 問題。
面對 Over-fetching 跟 Under-fetching,業界一個常見解法是 GraphQL。
讓 Client 自己描述「我要什麼形狀的資料」,Server 收到之後只回傳剛好符合的內容,一次搞定。
同樣是拿 Feed 加上作者暱稱的需求,GraphQL 的 Query 大概長這樣:
query {
feed {
content
author {
nickname
}
}
}
Server 回傳:
{
"data": {
"feed": [
{ "content": "Hello PokeThreads", "author": { "nickname": "Pikachu" } }
]
}
}
一次 Request,剛好拿到 content 跟 author.nickname,沒有多餘欄位,也不需要額外再打一次 GET /users/:id。

| REST | GraphQL | |
|---|---|---|
| 資料形狀 | Server 決定,固定 | Client 決定,彈性 |
| Over-fetching | 容易發生 | 幾乎不會 |
| Under-fetching / N+1 | 容易發生 | 一次 Query 解決 |
| HTTP Cache | 天生支援(GET 可被 CDN/瀏覽器快取) | 較難直接套用(多半是 POST) |
| 後端複雜度 | 低,路由對應清楚 | 較高,需要 Schema、Resolver |
| 常見場景 | 單一種前端、Public API | 多種平台(Web/iOS/Android 需求不同)、大型內部系統 |
GraphQL 解決了資料形狀的問題,但也把一部分複雜度從前端搬到了後端:
feed { author { nickname } } 如果每篇貼文都各自查一次作者,20 篇貼文一樣會變成 20 次 DB Query,只是問題從「前端打 20 次 API」變成「後端跑 20 次 Query」,通常要靠 Batching(如 DataLoader) 把同一批 Request 合併成一次查詢。GET /feed 是一個固定網址,瀏覽器、CDN 都能直接快取;GraphQL 通常用同一個 /graphql 端點搭配 POST,沒辦法直接套用這套現成的 HTTP Cache 機制,得自己在應用層另外處理。回到 PokeThreads 目前的規模:使用者只透過一種 Web 前端,每個畫面需要的資料形狀都算固定,REST 的 Over-fetching / Under-fetching 問題還不明顯,換成 GraphQL 反而是拿簡單問題換複雜方案。
所以這個系列會繼續用 REST 把 PokeThreads 的後端搭起來,維持簡單也保留 HTTP Cache 這個之後很好用的優化空間。但如果之後 PokeThreads 要同時支援 Web、iOS、Android,而且每個平台想要的資料欄位都不太一樣,GraphQL 讓 Client 自己決定資料形狀的彈性,就會變成很值得認真考慮的選項。
這只是前後端溝通方式的其中一層,不管選 REST 還是 GraphQL,最終還是要回到 Server 本身:這幾天我們的 Feed、Post、Follow,全部都靠同一台 Server 在處理。使用者一旦變多,這台 Server 撐不撐得住,才是接下來真正要面對的問題。